Jestメモ Day2
Jestメモ Day2
テストユーティリティの謎の挙動
結論: ReactTestUtilsを使うのをやめる
とりあえずまずはコンポーネントをレンダリングして観察しようと思った
await render(<ShowLog talk="SJSLzd0PCLcJ3Nzlfdc4" />);
code::
(node:40175) UnhandledPromiseRejectionWarning: Error: FIRESTORE (7.24.0) INTERNAL ASSERTION FAILED: Unexpected state
(node:40175) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag --unhandled-rejections=strict (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 36) (node:40175) PromiseRejectionHandledWarning: Promise rejection was handled asynchronously (rejection id: 36)
うーん、どうやらawaitでFirestoreにアクセスするPromiseを呼ぶとエラーになるっぽい
一方でawaitしないと、データを得る前に次のコードに処理が移ってしまう
「データを読み込む→setGlobalする」というコードを2つに分けて、データを取り終わったところでPromiseを返すようにし、それのthenでexpectする doc code:ts
test("show log", () => {
return loadLogsFromFirestore("SJSLzd0PCLcJ3Nzlfdc4").then((talkObject) => {
expect(talkObject).toMatchSnapshot();
});
ダメだ、それでも FIRESTORE (7.24.0) INTERNAL ASSERTION FAILED: Unexpected state
テストコードのなかでFirestoreにアクセスするのをやめよう
Firestoreは自前でリトライをしたりとかするのでテストの中で触らない方が良さそう
アプリのトップレベルでFirestoreから取った値を表示する
code:ts
function App() {
loadLogsFromFirestore("SJSLzd0PCLcJ3Nzlfdc4").then((talkObject) => {
setLogs(JSON.stringify(talkObject));
});
return <pre>{logs}</pre>;
この内容をtalkObject.jsonに保存
import * as MockTalkObject from "./talkObject.json";で読める
Firestoreから読む関数をモックで置き換える
code:ts
const m = jest
.spyOn(loadLogsModule, "loadLogsFromFirestore")
.mockResolvedValue(MockTalkObject);
モックしてるのにエラーになる
これ同一モジュール内の関数呼び出しはモックしても置き換わらない風の挙動だな
モックする関数を独立のモジュールにしたら動いた
ここまで動いた
code:ts
test("show log", () => {
const m = jest
.spyOn(loadLogsModule, "loadLogsFromFirestore")
.mockResolvedValue(MockTalkObject);
// no talkID needed beacuse loadLogsFromFirestore is mocked
render(<ShowLog talk="" />);
screen.debug();
});
しかしまだ目的のテストには辿り着かない
ReactのsetStateが非同期だからこれをやっても「データは受け取ったがまだ画面の再描画が行われてない状態」のDOMが得られる
code:ts
const { rerender } = render(<ShowLog talk="" />);
rerender(<ShowLog talk="" />);
code::
console.error
Warning: An update to ShowLog inside a test was not wrapped in act(...).
When testing, code that causes React state updates should be wrapped into act(...):
act(() => {
/* fire events that update state */
});
/* assert on the output */
いや、rerender以前にこのコードだけでも同じエラーメッセージが出るな。actで包めと言われたものを包んでるのにエラーになる
code:ts
act(() => {
render(<ShowLog talk="" />);
});
特定のエレメントが出現するまで待ってるな
await waitFor(() => screen.getByRole("heading"))
これ再レンダリングが行われたかどうか知る方法がないから要素があるかの判定を何度も繰り返して待つ風の引数だな
しかし今回のテスト対象、ログのロード待ちなので特に新しく出現するエレメントがない
いやまてよ、ログデータはモックで固定されてるので、ログに出現する適当な文字列が出現するのを待てば良いのか
できた、debugでログがレンダリングされてるのを確認した
code:ts
test("show log", async () => {
const m = jest
.spyOn(loadLogsModule, "loadLogsFromFirestore")
.mockResolvedValue(MockTalkObject);
// no talkID needed beacuse loadLogsFromFirestore is mocked
render(<ShowLog talk="" />);
await waitFor(() => screen.getByText("🙁"));
screen.debug();
});
メニューからエクスポートを選んだ時にエクスポート内容として出力されるテキストをスナップショットする
code:ts
const m2 = jest.spyOn(RegroupDialogModule, "openRegroupDialog");
fireEvent.click(screen.getByLabelText("menu"));
fireEvent.click(screen.getByText("Export for Regroup"));
expect(m2).toHaveBeenCalled();
expect(m2.mock.calls00).toMatchSnapshot(); [0][0]は初回呼び出しの1つ目の引数という意味
これは期待通りテストできた
昨日
https://gyazo.com/29e227d54e0659616f0efb73f7f60c1c
今日
https://gyazo.com/08401b0c38956da44c29a55d969cefa8
めでたしめでたし
残り
https://gyazo.com/2e99067085ced9ee46727cb443ad1fb5
あ、昨日のテストはフレームワークを変えるにあたってコメントアウトしたんだった。だからNewTalkのカバレッジが低いんだな。
次は昨日のテストを新しい方式で書き直すか。
--- matome
We recommend using React Testing Library
React Testing Library:
The @testing-library family of packages helps you test UI components in a user-centric way. --- doc The DOM Testing Library is a very light-weight solution for testing DOM nodes (whether simulated with JSDOM as provided by default with Jest or in the browser) doc Jest